﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace PhotonControls
{
    public partial class FImage : UserControl
    {

        #region Private constants 
        private const float MAX_ZOOM = 10.0F;
        private const float MIN_ZOOM = 0.1F;
        private const float ZOOM_STEP = 0.1F;
        #endregion

        #region Private variables
        private Bitmap m_Image;
        private String m_FileName;
        private float m_Scale;
        private Point m_StartPoint;

        private bool m_Pressed;
        private int m_X;
        private int m_Y;

        private FImageExtMetadata m_ExtMetadata;
        private FImageTool m_Tool;
        private Bitmap m_background; 
        #endregion 

        public FImage()
        {
            InitializeComponent();
            m_Scale = 1;
            m_FileName = "";
            m_StartPoint = new Point(0, 0);
            m_Image = null;
            this.BackColor = Color.Black;
            m_Pressed = false;
            m_X = 0;
            m_Y = 0;

            m_ExtMetadata = null;
            m_Tool = null;
            m_background = new Bitmap(Width, Height);
        }
       
        #region Private helpers 
        private int _GetWidthToDisplay()
        {
            if (m_Image.Width * m_Scale > Width)
                return Width;
            else
                return (int)(m_Image.Width * m_Scale);
        }
        private int _GetHeightToDisplay()
        {
            if (m_Image.Height * m_Scale > Height)
                return Height;
            else
                return (int)(m_Image.Height * m_Scale);        
        }
        private Rectangle _GetInternalVisibleArea()
        {
            
            int _top, _bottom, _left, _right;
            _top = m_StartPoint.Y;
            _bottom = _top + (int)(_GetHeightToDisplay() / m_Scale);

            _left = m_StartPoint.X;
            _right = _left + (int)(_GetWidthToDisplay() / m_Scale); 

            return new Rectangle(_left, _top, _right-_left, _bottom - _top);
        }
        #endregion

        #region Rendereing
        private void _DrawEmptyString(Graphics g) 
        {
            System.Drawing.Graphics formGraphics = g;
            string drawString = "FImage.net";
            System.Drawing.Font drawFont = new System.Drawing.Font("Arial", 16);
            System.Drawing.SolidBrush drawBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black);


            // Measure string.
            SizeF stringSize = new SizeF();
            stringSize = g.MeasureString(drawString, drawFont);
            
            float x = Width / 2 - stringSize.Width / 2;
            float y = Height / 2 - stringSize.Height / 2;
            drawBrush.Color = Color.Green;
            System.Drawing.StringFormat drawFormat = new System.Drawing.StringFormat();
            formGraphics.DrawString(drawString, drawFont, drawBrush, x, y, drawFormat);
            drawFont.Dispose();
            drawBrush.Dispose();
            formGraphics.Dispose();
        }

        private void _DrawBitmap(Graphics g)
        {
            if (m_Image == null)
                return;

            Bitmap surface = m_background;//new Bitmap(Width, Height);
            Graphics canvas = Graphics.FromImage(surface);
            canvas.Clear(Color.Black);
            int _left, _top, _right, _bottom;

            _left = 0;
            _right = _GetWidthToDisplay();
            _top = 0;
            _bottom = _GetHeightToDisplay();

            Rectangle src_r = _GetInternalVisibleArea();

            // отцентрируем изображение
            if (_right < Width)
            {
                _left = (Width - _right) / 2;
                _right = _left + _right;
            }

            if (_bottom < Height)
            {
               _top = (Height - _bottom) / 2;
                _bottom = _top + _bottom;           
            }
            Rectangle dst_r = new Rectangle(_left, _top, _right-_left, _bottom- _top);
            canvas.DrawImage(m_Image, dst_r, src_r, GraphicsUnit.Pixel);
            
            g.DrawImage(surface, new Point(0, 0));
        }

        private void _UpdateDisplay()
        {
            Graphics g = Graphics.FromHwnd(Handle);
            _DrawBitmap(g);
        }
        #endregion

        #region File IO operations

        /* выполняет сохранение любого объекта в xml файл
         */
        public static bool SaveXml(object obj, string filename)
        {
            bool result = false;
            using (StreamWriter writer = new StreamWriter(filename))
            {
                try
                {
                    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                    ns.Add("", "");
                    XmlSerializer serializer = new XmlSerializer(obj.GetType());
                    serializer.Serialize(writer, obj, ns);
                    result = true;
                }
                catch (Exception e)
                {
                    // Логирование
                }
                finally
                {
                    writer.Close();
                }
            }
            return result;
        }
        /* выполняет инициализацию объекта из xml файла
         */
        public static object LoadXml(Type type, string filename)
        {
            object result = null;
            using (StreamReader reader = new StreamReader(filename))
            {
                try
                {
                    XmlSerializer serializer = new XmlSerializer(type);
                    result = serializer.Deserialize(reader);
                }
                catch (Exception e)
                {
                    // Логирование
                }
                finally
                {
                    reader.Close();
                }
            }
            return result;
        }
        public void LoadImage(string fileName)
        {
            string strXMLName = "";
            if (m_Image != null)
            {
                m_Image = null;
                if (m_ExtMetadata != null)
                {
                    strXMLName = System.IO.Path.ChangeExtension(m_FileName, ".xml");
                    SaveXml(m_ExtMetadata, strXMLName);
                }
            }
            m_Image = null;
            m_ExtMetadata = null;
            m_Image = new Bitmap(fileName);
            m_FileName = fileName;
            strXMLName = System.IO.Path.ChangeExtension(fileName, ".xml");
            if (System.IO.File.Exists(strXMLName))
            {
                m_ExtMetadata = null;
                m_ExtMetadata = LoadXml(typeof(FImageExtMetadata ), strXMLName) as FImageExtMetadata;
            }
            BestFit();
        }
        #endregion 

        #region Fitting
        public void BestFit()
        {
            if (m_Image == null)
                return;
            m_StartPoint.X = 0;
            m_StartPoint.Y = 0;

            float alfa_c = (float)Width / (float)Height;
            float alfa_i = (float)m_Image.Width / (float)m_Image.Height;
            if (alfa_c > alfa_i)
            {
                m_Scale = (float)Height / (float)m_Image.Height;
            }
            else
            {
                m_Scale = (float)Width / (float)m_Image.Width;
            }
            _UpdateDisplay();
        }
        public void FitWidth()
        {
            if (Empty)
                return;
            m_Scale = Width / (float)m_Image.Width;
            m_StartPoint.Y = 0;
            m_StartPoint.X = 0;
            _UpdateDisplay();
        }
        public void FitHeight()
        {
            if (Empty)
                return;
            m_Scale = Height / (float)m_Image.Height;
            m_StartPoint.Y = 0;
            m_StartPoint.X = 0;
            _UpdateDisplay();
        }

        #endregion 

        #region Coordiantes Transform
        int GetImageX(int ScreenX)
        {
            if (m_Image == null)
                return -1;
            int display_w = _GetHeightToDisplay();
            int _left, _right;
            if (display_w < Width)
            {
                _left = (Width - display_w) / 2;
                _right = _left + display_w;
                if (ScreenX < _left || ScreenX >= _right)
                    return -1;
            }
            else
            {
                _left = 0;
                _right = display_w;
            }
            int sx = (int)(m_StartPoint.X + (float)(ScreenX - _left) / m_Scale);
            return (sx >= 0 && sx <= m_Image.Width)? sx:-1;
        }
        int GetImageY(int ScreenY)
        {
            if (m_Image == null)
                return -1;
            int display_h = _GetHeightToDisplay();
            int _top, _bottom;
            int Sy = Height - ScreenY;
            if (display_h < Height)
            {
                _top = (Height - display_h) / 2;
                _bottom = _top + display_h;

                if (Sy <= _top || Sy >= _bottom)
                    return -1;
            }
            else
            {
                _top = 0;
                _bottom = display_h;
            }
            int sy = (int)(m_Image.Height - m_StartPoint.Y - (Sy - _top) / m_Scale);
            return (sy >= 0 && sy <= m_Image.Height) ? sy : -1;
        }
        #endregion 

        #region Zoom support
        private Point _VisibleCenter()
        {
            Point Center = new Point();

            if (m_Image.Height * m_Scale >= Height)
                Center.Y = (int)((float)m_StartPoint.Y + (float)Height / (2.0*m_Scale) + 0.5);
            else 
                Center.Y = (int)((float)m_StartPoint.Y + (float)m_Image.Height / 2.0 + 0.5);
            
            if (m_Image.Width * m_Scale >= Width)
                Center.X = (int)((float)m_StartPoint.X + (float)Width/(2.0*m_Scale) + 0.5);
            else
                Center.X = (int)((float)m_StartPoint.X + (float)m_Image.Width / 2.0 + 0.5);
            return Center;
        }
        private void _CorrectStartPoint(Point c)
        {

            if (m_Image.Width * m_Scale > Width)
                m_StartPoint.X = (int)(c.X - Width / (2 * m_Scale));
            else
                m_StartPoint.X = 0;
            if (m_Image.Height * m_Scale > Height)
                m_StartPoint.Y = (int)(c.Y - Height / (2 * m_Scale));
            else
                m_StartPoint.Y = 0;

            int Cx1, Cy1;
            if (m_Image.Height * m_Scale >= Height)
                Cy1 = (int)(m_StartPoint.Y + Height / (2 * m_Scale));
            else
                Cy1 = m_StartPoint.Y + m_Image.Height / 2;
            if (m_Image.Width * m_Scale >= Width)
                Cx1 = (int)(m_StartPoint.X + Width / (2 * m_Scale));
            else
                Cx1 = (int)(m_StartPoint.X + m_Image.Width / 2);

            m_StartPoint.X += c.X - Cx1;
            m_StartPoint.Y += c.Y - Cy1;

            m_StartPoint.X = (m_StartPoint.X < 0) ? 0 : m_StartPoint.X;
            m_StartPoint.X = ((int)(m_StartPoint.X + (float)_GetWidthToDisplay() / m_Scale) > m_Image.Width) ? (int)(m_Image.Width - _GetWidthToDisplay() / m_Scale) : m_StartPoint.X;

            m_StartPoint.Y = (m_StartPoint.Y < 0) ? 0 : m_StartPoint.Y;
            m_StartPoint.Y = (m_StartPoint.Y + _GetHeightToDisplay() / m_Scale > m_Image.Height) ? (int)(m_Image.Height - _GetHeightToDisplay() / m_Scale) : m_StartPoint.Y;

        }
        public void ZoomIn()
        {
            if (m_Image == null || m_Scale > MAX_ZOOM - ZOOM_STEP)
                return;

            Point c = _VisibleCenter();

            m_Scale = m_Scale + ZOOM_STEP;

            _CorrectStartPoint(c);
            _UpdateDisplay();
        }
        public void ZoomOut()
        {
            if (m_Image == null || m_Scale < MIN_ZOOM + ZOOM_STEP)
                return;

            Point c = _VisibleCenter();
            m_Scale = m_Scale - ZOOM_STEP;

            _CorrectStartPoint(c);
            _UpdateDisplay();
        }

        public void ZoomTo(int Factor)
        {
            float f = (float)Factor / 100.0f;
            if (m_Image == null || f < MIN_ZOOM || f > MAX_ZOOM)
                return;
            Point c = _VisibleCenter();
            m_Scale = f;

            _CorrectStartPoint(c);
            _UpdateDisplay();
        }
        // rect - прямоугольник в координатах экрана. 
        public void ZoomToRect(Rectangle rect)
        {
            if (m_Image == null)
                return;
            if (rect.Width <= 0 || rect.Height <= 0)
                return;

            int _iLeft = GetImageX(rect.Left);
            int _iTop = GetImageY(rect.Top);
            int _iRight = GetImageX(rect.Right);
            int _iBottom = GetImageY(rect.Bottom);

            if (_iLeft < 0 || _iRight < 0 || _iTop < 0 || _iBottom < 0)
                return;



            //int Cx = (int)(GetImageX(rect.Left) + rect.Width / (2 * m_Scale));
            //int Cy = (int) (m_Image.Height - GetImageY(rect.Top) - rect.Height / (2*m_Scale));

            //float alfa_c = (float)Width / (float)Height;
            //float alfa_r = (float)rect.Width / (float)rect.Height;

            //if (alfa_c >= alfa_r)
            //    m_Scale = (float)Height * m_Scale / (float)rect.Height;
            //else
            //    m_Scale = (float)Width * m_Scale / (float)rect.Width;
            //if (m_Image.Height * m_Scale > Height)
            //    m_StartPoint.Y = (int)(Cy - Height / (2 * m_Scale));
            //else
            //    m_StartPoint.Y = 0;
            //if (m_Image.Width * m_Scale > Width)
            //    m_StartPoint.X = (int)(Cx - Width / (2 * m_Scale));
            //else
            //    m_StartPoint.X = 0;

            //m_StartPoint.X = (m_StartPoint.X < 0) ? 0 : m_StartPoint.X;
            //m_StartPoint.X = ((int)(m_StartPoint.X + (float)_GetWidthToDisplay() / m_Scale) > m_Image.Width) ? (int)(m_Image.Width - _GetWidthToDisplay() / m_Scale) : m_StartPoint.X;

            //m_StartPoint.Y = (m_StartPoint.Y < 0) ? 0 : m_StartPoint.Y;
            //m_StartPoint.Y = (m_StartPoint.Y + _GetHeightToDisplay() / m_Scale > m_Image.Height) ? (int)(m_Image.Height - _GetHeightToDisplay() / m_Scale) : m_StartPoint.Y;

            _UpdateDisplay();
        }
        public void ActualSize()
        {
            if (m_Image == null)
                return;
            ZoomTo(100);
           _UpdateDisplay();
        }
        #endregion

        #region Pane support
        public void MoveToCenter()
        {
            if (m_Image == null)
                return;
            if (_GetHeightToDisplay() < Height && _GetWidthToDisplay() < Width)
                return;

            m_StartPoint.X = (int)(m_Image.Width / 2 - Width / (2*m_Scale));
            m_StartPoint.Y = (int)(m_Image.Height / 2 - Height / (2 * m_Scale));
            _UpdateDisplay();
        }
        public void MoveToLeftTop()
        {
            if (m_Image == null)
                return;
            if (_GetHeightToDisplay() < Height && _GetWidthToDisplay() < Width)
                return;
            m_StartPoint.X = 0;
            m_StartPoint.Y = (int)(m_Image.Height - Height / m_Scale);
            _UpdateDisplay();
        }
        public void MoveToRightBottom()
        {
            if (m_Image == null)
                return;
            if (_GetHeightToDisplay() < Height && _GetWidthToDisplay() < Width)
                return;
            m_StartPoint.X = (int)( m_Image.Width - Width / m_Scale);
            m_StartPoint.Y = 0;
            _UpdateDisplay();
        }
        public void MoveTo(int x, int y)
        {
            if (m_Image == null)
                return;
            if (_GetWidthToDisplay() < Width && _GetHeightToDisplay() < Height)
                return;
            int _x, _y;
            _x = x;
            _y = m_Image.Height - y;
            
            m_StartPoint.X =(int)( _x - Width/ (2*m_Scale));
            m_StartPoint.Y = (int)(_y - Height / (2 * m_Scale));

            if ((int)(m_StartPoint.X + Width / m_Scale) > m_Image.Width)
                m_StartPoint.X = (int)(m_Image.Width - Width / m_Scale);
            else if (m_StartPoint.X < 0)
                m_StartPoint.X = 0;

            if ((int)(m_StartPoint.Y + Height / m_Scale) > m_Image.Height)
                m_StartPoint.Y = (int)(m_Image.Height - Height / m_Scale);
            else if (m_StartPoint.Y < 0)
                m_StartPoint.Y = 0;

            _UpdateDisplay();

        }
        public void MoveBy(int dx, int dy)
        {
            if (m_Image == null)
                return;
            float _dx = (float)dx / m_Scale;
            float _dy = (float)dy / m_Scale;

            if (m_Image.Width * m_Scale <= (float)Width &&
                m_Image.Height * m_Scale <= (float)Height)
                return;
            if (m_Image.Width * m_Scale > (float)Width)
            {
                if (m_StartPoint.X + _dx > m_Image.Width - Width / m_Scale)
                {
                    m_StartPoint.X = (int)(m_Image.Width - Width / m_Scale);
                }
                else if (m_StartPoint.X + _dx < 0)
                    m_StartPoint.X = 0;
                else
                    m_StartPoint.X += (int)_dx;
            }

            if (m_Image.Height * m_Scale > (float)Height)
            {
                if (m_StartPoint.Y - _dy > m_Image.Height - Height / m_Scale)
                { 
                    m_StartPoint.Y = (int)(m_Image.Height - Height / m_Scale);
                }
                else if (m_StartPoint.Y - _dy < 0)
                {
                    m_StartPoint.Y = 0;
                }
                else
                {
                    m_StartPoint.Y += (int)_dy;
                }
            }
            _UpdateDisplay();
        }
        #endregion

        #region Override methods
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (m_Image == null)
            {
                _DrawEmptyString(e.Graphics);
            }
            else
            {
                _DrawBitmap(e.Graphics);
            }
        }
        protected override void OnMouseWheel(MouseEventArgs e)
        {
            if (e.Delta > 0)
            {
                ZoomIn();
            }
            else
            {
                ZoomOut();
            }
            base.OnMouseWheel(e); 
        }
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            m_background = null;
            m_background = new Bitmap(Width, Height);
            BestFit();
        }
        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Left)
                return;
            if (m_Tool == null)
            {
                m_X = e.X;
                m_Y = e.Y;
                m_Pressed = true;
            }
            else
                m_Tool.MouseDown(e);

            base.OnMouseDown(e);
        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (m_Tool == null)
            {
                if (m_Pressed)
                    MoveBy(m_X - e.X, m_Y - e.Y);
                m_X = e.X;
                m_Y = e.Y;
            }
            else
                m_Tool.MouseMove(e);
            base.OnMouseMove(e);
        }
        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Left)
                return;
            if (m_Tool == null)
            {
                if (m_Pressed)
                {
                    // todo: курсор мыши
                }
                m_X = 0;
                m_Y = 0;
                m_Pressed = false;
            }
            else
                m_Tool.MouseUp(e);
            base.OnMouseUp(e);
        }
        #endregion

        #region Public Properties
        public int Zoom
        {
            get { return GetZoomFactor(); }
            //set { SetZoomFactor(value); }
        }
        public bool Empty
        {
            get { return GetEmpty(); }
        }
        public string Description
        {
            get { return GetDescription();}
            set { SetDescription(value); }
        }
        public FImageTool Tool
        { 
            get {return m_Tool;}
            set {m_Tool = value;}
        }
        public Bitmap Background
        {
            get { return GetBackGround(); }
            set { SetBackGround(value); }
        }
        #endregion 


        #region Properties Implementation
        int GetZoomFactor()
        {
            return (int)(m_Scale * 100);
        }
        bool GetEmpty()
        {
            return m_Image == null; 
        }

        string GetDescription()
        {
            if (m_ExtMetadata == null)
                return "";
            return m_ExtMetadata.Comments;
        }
        void SetDescription(string Value)
        {
            if (m_ExtMetadata == null)
                m_ExtMetadata = new FImageExtMetadata();
            m_ExtMetadata.Comments = Value; 
        }

        Bitmap GetBackGround()
        {
            return m_background;
        }
        void SetBackGround(Bitmap value)
        {
            m_background = value;
            Graphics g = Graphics.FromHwnd(Handle);
            g.DrawImageUnscaled(m_background, 0, 0, Width, Height);
        }

        #endregion 

    }
}
